/*
*    Qizx/open 3.0 beta
*
* This code is the open-source version of Qizx.
* Copyright (C) 2004-2009 Axyana Software -- All rights reserved.
*
* The contents of this file are subject to the Mozilla Public License 
*  Version 1.1 (the "License"); you may not use this file except in 
*  compliance with the License. You may obtain a copy of the License at
*  http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
*  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
*  for the specific language governing rights and limitations under the
*  License.
*
* The Initial Developer of the Original Code is Xavier Franc - Axyana Software.
*
*/
using System;
using EvaluationException = System.Qizx.Api.EvaluationException;
using DateTimeException = System.Qizx.Api.Util.XQDateTimeException;
using Duration = System.Qizx.Api.Util.XQDuration;
using Conversion = System.Qizx.Xdm.Conversion;
using System.Qizx.XQuery;
using SingleDecimal = System.Qizx.XQuery.Dt.SingleDecimal;
using SingleDouble = System.Qizx.XQuery.Dt.SingleDouble;
using SingleDuration = System.Qizx.XQuery.Dt.SingleDuration;
using SingleFloat = System.Qizx.XQuery.Dt.SingleFloat;
using SingleInteger = System.Qizx.XQuery.Dt.SingleInteger;
using SingleItem = System.Qizx.XQuery.Dt.SingleItem;
using Function = System.Qizx.XQuery.Fn.Function;
using Prototype = System.Qizx.XQuery.Fn.Prototype;
namespace System.Qizx.XQuery.Op {

    /// <summary> Implementation of operator '*'.</summary>
    public class MulOp : NumericOp {
        virtual public Prototype[] Protos {
            get {
                return null;
            }

        }
        public MulOp(Expression expr1, Expression expr2)
            : base(expr1, expr2) {
        }

        public override void Dump(ExprDisplay d) {
            d.header(this);
            d.child("expr1", operands[0]);
            d.child("expr2", operands[1]);
        }

        public override Expression StaticCheck(ModuleContext context, int flags) {
            switch (combinedArgTypes(context)) {

                case XQType.INT_INT:
                    return this.transfer(new ExecInt(), operands);

                case XQType.INT_DOUBLE:
                case XQType.DOUBLE_INT:
                case XQType.DOUBLE_DEC:
                case XQType.DEC_DOUBLE:
                case XQType.FLOAT_DOUBLE:
                case XQType.DOUBLE_FLOAT:
                case XQType.DOUBLE_DOUBLE:
                    return transfer(new ExecDouble(), operands);

                default:
                    return transfer(new ExecAny(), operands);

            }
        }

        public class ExecInt : Function.OptIntegerCall {

            public override long evalAsOptInteger(Focus focus, EvalContext context) {
                long e1 = args[0].evalAsOptInteger(focus, context);
                long e2 = args[1].evalAsOptInteger(focus, context);
                context.at(this);
                if (!Conversion.isIntegerRange(e1 * (double)e2))
                    context.error("FOAR0002", this, "integer overflow");
                return e1 * e2;
            }
        }

        public class ExecDouble : Function.OptDoubleCall {
            public override double evalAsOptDouble(Focus focus, EvalContext context) {
                double e1 = args[0].evalAsOptDouble(focus, context);
                double e2 = args[1].evalAsOptDouble(focus, context);
                context.at(this);
                // System.out.println("add "+e1+" to "+e2);
                return e1 * e2;
            }
        }

        public class ExecAny : Function.NumericCall {
            public override XQValue eval(Focus focus, EvalContext context) {
                XQItem op1 = args[0].evalAsOptItem(focus, context);
                XQItem op2 = args[1].evalAsOptItem(focus, context);
                context.at(this);
                if (op1 == null || op2 == null)
                    return System.Qizx.XQuery.XQValues.Empty;

                switch (System.Qizx.XQuery.Op.NumericOp.combinedArgTypes(op1, op2)) {

                    case XQType.INT_INT:
                        long i1 = op1.ValueAsInteger, i2 = op2.ValueAsInteger;
                        if (!Conversion.isIntegerRange(i1 * (double)i2))
                            context.error("FOAR0002", this, "integer overflow");
                        return new SingleInteger(i1 * i2);


                    case XQType.INT_DEC:
                    case XQType.DEC_INT:
                    case XQType.DEC_DEC:
                        return new SingleDecimal(System.Decimal.Multiply(op1.ValueAsDecimal, op2.ValueAsDecimal));


                    case XQType.INT_FLOAT:
                    case XQType.FLOAT_INT:
                    case XQType.FLOAT_DEC:
                    case XQType.DEC_FLOAT:
                    case XQType.FLOAT_FLOAT:
                        return new SingleFloat(op1.ValueAsFloat * op2.ValueAsFloat);


                    case XQType.INT_DOUBLE:
                    case XQType.DOUBLE_INT:
                    case XQType.DOUBLE_DEC:
                    case XQType.DEC_DOUBLE:
                    case XQType.FLOAT_DOUBLE:
                    case XQType.DOUBLE_FLOAT:
                    case XQType.DOUBLE_DOUBLE:
                        return new SingleDouble(op1.ValueAsDouble * op2.ValueAsDouble);


                    case XQType.YMDUR_DOUBLE:
                    case XQType.YMDUR_FLOAT:
                    case XQType.YMDUR_DEC:
                    case XQType.YMDUR_INT:
                        return mulYM(op1.Duration, op2.ValueAsDouble, context);


                    case XQType.DOUBLE_YMDUR:
                    case XQType.FLOAT_YMDUR:
                    case XQType.DEC_YMDUR:
                    case XQType.INT_YMDUR:
                        return mulYM(op2.Duration, op1.ValueAsDouble, context);


                    case XQType.DTDUR_DOUBLE:
                    case XQType.DTDUR_FLOAT:
                    case XQType.DTDUR_DEC:
                    case XQType.DTDUR_INT:
                        return mulDT(op1.Duration, op2.ValueAsDouble, context);


                    case XQType.DOUBLE_DTDUR:
                    case XQType.FLOAT_DTDUR:
                    case XQType.DEC_DTDUR:
                    case XQType.INT_DTDUR:
                        return mulDT(op2.Duration, op1.ValueAsDouble, context);


                    default:
                        context.error(System.Qizx.XQuery.Op.Expression.ERRC_BADTYPE, this, "invalid types for operator *");
                        break;

                }
                return new SingleItem(op1);
            }

            private XQValue mulDT(Duration duration, double sc, EvalContext context) {
                try {
                    if (System.Double.IsNaN(sc))
                        context.error("FOCA0005", this, "invalid argument");
                    if (System.Double.IsInfinity(sc))
                        context.error("FODT0002", this, "invalid argument");
                    return SingleDuration.newDT(Duration.newDayTime(duration.TotalSeconds * sc));
                } catch (DateTimeException e) {
                    context.error(System.Qizx.XQuery.Op.Expression.ERRC_BADTYPE, this, "OOPS " + e);
                    return null;
                }
            }

            private XQValue mulYM(Duration duration, double sc, EvalContext context) {
                try {
                    if (System.Double.IsNaN(sc))
                        context.error("FOCA0005", this, "invalid argument");
                    if (System.Double.IsInfinity(sc))
                        context.error("FODT0002", this, "invalid argument");
                    return SingleDuration.newYM(Duration.newYearMonth((int)System.Math.Round(duration.TotalMonths * sc)));
                } catch (DateTimeException e) {
                    context.error(System.Qizx.XQuery.Op.Expression.ERRC_BADTYPE, this, "OOPS " + e);
                    return null;
                }
            }
        }
    }
}